/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.Linq;
using Lucene.Net.Support;
using BufferedIndexInput = Lucene.Net.Store.BufferedIndexInput;
using Directory = Lucene.Net.Store.Directory;
using IndexInput = Lucene.Net.Store.IndexInput;
using IndexOutput = Lucene.Net.Store.IndexOutput;
using Lock = Lucene.Net.Store.Lock;

namespace Lucene.Net.Index
{
    
    
    /// <summary> Class for accessing a compound stream.
    /// This class implements a directory, but is limited to only read operations.
    /// Directory methods that would normally modify data throw an exception.
    /// </summary>
    public class CompoundFileReader : Directory
    {
        
        private readonly int readBufferSize;
        
        private sealed class FileEntry
        {
            internal long offset;
            internal long length;
        }

        private bool isDisposed;
        
        // Base info
        private readonly Directory directory;
        private readonly System.String fileName;
        
        private IndexInput stream;
        private HashMap<string, FileEntry> entries = new HashMap<string, FileEntry>();
        
        
        public CompoundFileReader(Directory dir, System.String name):this(dir, name, BufferedIndexInput.BUFFER_SIZE)
        {
        }
        
        public CompoundFileReader(Directory dir, System.String name, int readBufferSize)
        {
            directory = dir;
            fileName = name;
            this.readBufferSize = readBufferSize;
            
            bool success = false;
            
            try
            {
                stream = dir.OpenInput(name, readBufferSize);
                
                // read the directory and init files
                int count = stream.ReadVInt();
                FileEntry entry = null;
                for (int i = 0; i < count; i++)
                {
                    long offset = stream.ReadLong();
                    System.String id = stream.ReadString();
                    
                    if (entry != null)
                    {
                        // set length of the previous entry
                        entry.length = offset - entry.offset;
                    }

                    entry = new FileEntry {offset = offset};
                    entries[id] = entry;
                }
                
                // set the length of the final entry
                if (entry != null)
                {
                    entry.length = stream.Length() - entry.offset;
                }
                
                success = true;
            }
            finally
            {
                if (!success && (stream != null))
                {
                    try
                    {
                        stream.Close();
                    }
                    catch (System.IO.IOException)
                    {
                    }
                }
            }
        }

        public virtual Directory Directory
        {
            get { return directory; }
        }

        public virtual string Name
        {
            get { return fileName; }
        }

        protected override void Dispose(bool disposing)
        {
            lock (this)
            {
                if (isDisposed) return;
                if (disposing)
                {
                    if (entries != null)
                    {
                        entries.Clear();
                    }
                    if (stream != null)
                    {
                        stream.Close();
                    }
                }

                entries = null;
                stream = null;
                isDisposed = true;
            }
        }
        
        public override IndexInput OpenInput(System.String id)
        {
            lock (this)
            {
                // Default to readBufferSize passed in when we were opened
                return OpenInput(id, readBufferSize);
            }
        }
        
        public override IndexInput OpenInput(System.String id, int readBufferSize)
        {
            lock (this)
            {
                if (stream == null)
                    throw new System.IO.IOException("Stream closed");
                
                FileEntry entry = entries[id];
                if (entry == null)
                    throw new System.IO.IOException("No sub-file with id " + id + " found");
                
                return new CSIndexInput(stream, entry.offset, entry.length, readBufferSize);
            }
        }
        
        /// <summary>Returns an array of strings, one for each file in the directory. </summary>
        public override System.String[] ListAll()
        {
            return entries.Keys.ToArray();
        }
        
        /// <summary>Returns true iff a file with the given name exists. </summary>
        public override bool FileExists(System.String name)
        {
            return entries.ContainsKey(name);
        }
        
        /// <summary>Returns the time the compound file was last modified. </summary>
        public override long FileModified(System.String name)
        {
            return directory.FileModified(fileName);
        }
        
        /// <summary>Set the modified time of the compound file to now. </summary>
        public override void  TouchFile(System.String name)
        {
            directory.TouchFile(fileName);
        }
        
        /// <summary>Not implemented</summary>
        /// <throws>  UnsupportedOperationException  </throws>
        public override void  DeleteFile(System.String name)
        {
            throw new System.NotSupportedException();
        }
        
        /// <summary>Not implemented</summary>
        /// <throws>  UnsupportedOperationException  </throws>
        public void RenameFile(System.String from, System.String to)
        {
            throw new System.NotSupportedException();
        }
        
        /// <summary>Returns the length of a file in the directory.</summary>
        /// <throws>  IOException if the file does not exist  </throws>
        public override long FileLength(System.String name)
        {
            FileEntry e = entries[name];
            if (e == null)
                throw new System.IO.IOException("File " + name + " does not exist");
            return e.length;
        }
        
        /// <summary>Not implemented</summary>
        /// <throws>  UnsupportedOperationException  </throws>
        public override IndexOutput CreateOutput(System.String name)
        {
            throw new System.NotSupportedException();
        }
        
        /// <summary>Not implemented</summary>
        /// <throws>  UnsupportedOperationException  </throws>
        public override Lock MakeLock(System.String name)
        {
            throw new System.NotSupportedException();
        }
        
        /// <summary>Implementation of an IndexInput that reads from a portion of the
        /// compound file. The visibility is left as "package" *only* because
        /// this helps with testing since JUnit test cases in a different class
        /// can then access package fields of this class.
        /// </summary>
        public /*internal*/ sealed class CSIndexInput : BufferedIndexInput
        {
            internal IndexInput base_Renamed;
            internal long fileOffset;
            internal long length;

            private bool isDisposed;
            
            internal CSIndexInput(IndexInput @base, long fileOffset, long length):this(@base, fileOffset, length, BufferedIndexInput.BUFFER_SIZE)
            {
            }
            
            internal CSIndexInput(IndexInput @base, long fileOffset, long length, int readBufferSize):base(readBufferSize)
            {
                this.base_Renamed = (IndexInput) @base.Clone();
                this.fileOffset = fileOffset;
                this.length = length;
            }
            
            public override System.Object Clone()
            {
                var clone = (CSIndexInput) base.Clone();
                clone.base_Renamed = (IndexInput) base_Renamed.Clone();
                clone.fileOffset = fileOffset;
                clone.length = length;
                return clone;
            }
            
            /// <summary>Expert: implements buffer refill.  Reads bytes from the current
            /// position in the input.
            /// </summary>
            /// <param name="b">the array to read bytes into
            /// </param>
            /// <param name="offset">the offset in the array to start storing bytes
            /// </param>
            /// <param name="len">the number of bytes to read
            /// </param>
            public override void  ReadInternal(byte[] b, int offset, int len)
            {
                long start = FilePointer;
                if (start + len > length)
                    throw new System.IO.IOException("read past EOF");
                base_Renamed.Seek(fileOffset + start);
                base_Renamed.ReadBytes(b, offset, len, false);
            }
            
            /// <summary>Expert: implements seek.  Sets current position in this file, where
            /// the next <see cref="ReadInternal(byte[],int,int)" /> will occur.
            /// </summary>
            /// <seealso cref="ReadInternal(byte[],int,int)">
            /// </seealso>
            public override void  SeekInternal(long pos)
            {
            }

            protected override void Dispose(bool disposing)
            {
                if (isDisposed) return;

                if (disposing)
                {
                    if (base_Renamed != null)
                    {
                        base_Renamed.Close();
                    }
                }
                
                isDisposed = true;
            }
            
            public override long Length()
            {
                return length;
            }

            public IndexInput base_Renamed_ForNUnit
            {
                get { return base_Renamed; }
            }
        }
    }
}